//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;

namespace OpenLS.Spreadsheet.Extensibility
{
    public sealed class ParametersConverter
    {
        private readonly MethodInfo _method;

        private ParameterConversionData[] _parameterData;

        public ParametersConverter(MethodInfo method)
        {
            _method = method;
        }

        public MethodInfo Method
        {
            get { return _method; }
        }

        internal ParameterConversionData[] ParameterData
        {
            get
            {
                if (_parameterData == null)
                {
                    ParameterInfo[] parameters = Method.GetParameters();
                    _parameterData = new ParameterConversionData[parameters.Length];
                    for (int i = 0; i < parameters.Length; i++)
                    {
                        _parameterData[i] = new ParameterConversionData(parameters[i]);
                    }
                }
                return _parameterData;
            }
        }


        internal object[] TransformParameters(ICell target, IWorksheet worksheet, IWorkbook workbook, object[] args,
                                              ref Error error)
        {
            //\\ParameterInfo[] parameters = Method.GetParameters();
            if (ParameterData.Length == 0)
                return new object[0];
            int startOffset = 0;
            var newArguments = new object[ParameterData.Length];
            if (ParameterData[0].IsWorksheetContext)
            {
                // Contract.Assert(target != null);
                startOffset = 1;
                newArguments[0] = worksheet;
            }
            if (ParameterData[0].IsCellContext)
            {
                startOffset = 1;
                newArguments[0] = target;
            }
            for (int i = startOffset; i < ParameterData.Length; i++)
            {
                //\\ParameterInfo p = parameters[i];
                if (i >= args.Length + startOffset)
                {
                    if (ParameterData[i].IsCellContext)
                    {
                        newArguments[i] = target;
                        continue;
                    }
                    if (ParameterData[i].HasDefaultValue)
                    {
                        newArguments[i] = ParameterData[i].DefaultValue;
                        continue;
                    }
                        if (ParameterData[i].IsParams)
                        {
                            ConstructorInfo constructor = ParameterData[i].ParameterType.GetConstructor(new Type[]{typeof (int)});
                            newArguments[i] = constructor.Invoke(new object[] {0});
                            return newArguments;
                        }
                    else 
                    {
                        error = Errors.ValueError;
                        return newArguments;
                    }
                }
                int InIndex = i - startOffset;
                object inObject = args[InIndex];
                IParameterConverter c = ParameterData[i].Converter;
                if (inObject is Missing)
                {
                    newArguments[i] = ParameterData[i].DefaultValue;
                    continue;
                }
                if (ParameterData[i].IsParams)
                {
                    var l = new List<object>();
                    for (int j = InIndex; j < args.Length; j++)
                        l.Add(args[j]);
                    if (ParameterData[i].ParameterType == typeof(object[]))
                    {
                        if (ParameterData[i].Converter is CompositeArgumentConverter)
                        {
                            CompositeArgumentConverter compositeArgumentConverter = (CompositeArgumentConverter) ParameterData[i].Converter;
                            if (compositeArgumentConverter.ContainsNoConversion())
                            {
                                newArguments[i] = l.ToArray();
                                return newArguments;
                            }
                        }
                        var a = ToTArray<object>(l, workbook, worksheet, ref error, ParameterData[i].Converter);
                        newArguments[i] = a;
                        return newArguments;
                    }
                    if (ParameterData[i].ParameterType == typeof(string[]))
                    {
                        var a = ToTArray<string>(l, workbook, worksheet, ref error, ParameterData[i].Converter);
                        newArguments[i] = a;
                        return newArguments;
                    }
                    if (ParameterData[i].ParameterType == typeof(double[]))
                    {
                        var a = ToTArray<double>(l, workbook, worksheet, ref error, ParameterData[i].Converter);
                        newArguments[i] = a;
                        return newArguments;
                    }
                    if (ParameterData[i].ParameterType == typeof(int[]))
                    {
                        var a = ToTArray<int>(l, workbook, worksheet, ref error, ParameterData[i].Converter);
                        newArguments[i] = a;
                        return newArguments;
                    }
                    if (ParameterData[i].ParameterType == typeof(bool[]))
                    {
                        var a = ToTArray<bool>(l, workbook, worksheet, ref error, ParameterData[i].Converter);
                        newArguments[i] = a;
                        return newArguments;
                    }
                    if (ParameterData[i].ParameterType == typeof(IRange[]))
                    {
                        var a = ToTArray<IRange>(l, workbook, worksheet, ref error, ParameterData[i].Converter);
                        newArguments[i] = a;
                        return newArguments;
                    }
                    if (ParameterData[i].ParameterType == typeof(Complex[]))
                    {
                        var a = ToTArray<Complex>(l, workbook, worksheet, ref error, ParameterData[i].Converter);
                        newArguments[i] = a;
                        return newArguments;
                    }
                    if (ParameterData[i].ParameterType == typeof(DateTime[]))
                    {
                        var a = ToTArray<DateTime>(l, workbook, worksheet, ref error, ParameterData[i].Converter);
                        newArguments[i] = a;
                        return newArguments;
                    }
                    throw new NotImplementedException();
                }
                if (ParameterData[i].ParameterType == typeof(IEnumerable<Complex>))
                {
                    var realArgs = new object[args.Length - i];
                    for (int ii = 0; ii < realArgs.Length; ii++)
                        realArgs[ii] = args[ii + i];
                    newArguments[i] = toArray<Complex>(realArgs, workbook, worksheet, ref error,
                                                       ParameterData[i].Converter, ParameterData[i].IsParams);
                    continue;
                }
                if (ParameterData[i].ParameterType == typeof (double[]))
                {
                    var realArgs = new object[args.Length - i];
                    for (int ii = 0; ii < realArgs.Length; ii++)
                        realArgs[ii] = args[ii + i];
                    newArguments[i] = toDoubleArray(realArgs, workbook, worksheet, ref error, ParameterData[i].Converter,
                                                    ParameterData[i].IsParams);
                    if (ParameterData[i].IsParams)
                        return newArguments;
                    continue;
                }
                else if (ParameterData[i].ParameterType == typeof (double) ||
                         ParameterData[i].ParameterType == typeof (int))
                {
                    // object newValue;
                    var cd = new ConversionData(workbook, worksheet, inObject, ParameterData[i].ParameterType);
                    c.Convert(cd);

                    // if (!c.Convert(inObject, ParameterData[i].ParameterType, worksheet, ref ignore, ref error, out newValue))
                    //   newValue = inObject;
                    error = cd.Error;
                    if (cd.Error != null)
                        return null;
                    object newValue;
                    if (!cd.Successful)
                        newValue = inObject;
                    else
                    {
                        newValue = cd.Result;
                    }
                    newArguments[i] = newValue;
                    continue;
                }
                else if (ParameterData[i].ParameterType == typeof (string))
                {
                    object newValue;
                    var d = new ConversionData(workbook, worksheet, inObject, ParameterData[i].ParameterType);
                    c.Convert(d);
                    // if (!d.Successful)
                    //if (!c.Convert(inObject, ParameterData[i].ParameterType, worksheet, ref ignore, ref error, out newValue))
                    //newValue = inObject;
                    error = d.Error;
                    if (d.Error != null)
                        return null;
                    if (!d.Successful)
                        newValue = inObject;
                    else
                    {
                        newValue = d.Result;
                    }
                    newArguments[i] = newValue;
                    //\\newArguments[i] = System.Convert.ToString(inObject);
                    continue;
                }
                else if (true)
                {
                    object newValue;
                    var cd = new ConversionData(workbook, worksheet, inObject, ParameterData[i].ParameterType);
                    c.Convert(cd);
                    error = cd.Error;
                    if (cd.Error != null)
                        return null;
                    //  if (!c.Convert(inObject, ParameterData[i].ParameterType, worksheet, ref ignore, ref error, out newValue))
                    //    newValue = inObject;
                    // if (error != null)
                    //   return null;!
                    if (!cd.Successful)
                    {
                            newValue = inObject;
                    }
                    else
                    {
                        newValue = cd.Result;
                    }
                    newArguments[i] = newValue;
                    continue;
                }
            }
            return newArguments;
        }


        private static T[] toArray<T>(object[] args, IWorkbook workbook, IWorksheet worksheet, ref Error error,
                                      IParameterConverter converter, bool isParams)
        {
            if (converter == null)
                converter = CanonicalArgumentConverter.Instance;
            var result = new List<T>();
            bool already = false;
            foreach (object argument in args)
            {
                if (already && !isParams)
                    break;
                already = true;
                if (argument is IEnumerable<object>)
                {
                    var range = (IEnumerable<object>) argument;
                    foreach (object cell in range)
                    {
                        // bool ignore = false;
                        object o;
                        var cd = new ConversionData(workbook, worksheet, cell, typeof (T));
                        converter.Convert(cd);
                        o = !cd.Successful ? cell : cd.Result;

                        if (error != null)
                            return null;
                        if (cd.Ignore)
                            continue;
                        result.Add((T) o);
                    }
                }
                else
                {
                    // bool ignore = false;
                    var cd = new ConversionData(workbook, worksheet, argument, typeof (T));
                    converter.Convert(cd);
                    //if (!converter.Convert(argument, typeof(T), worksheet, ref ignore, ref error, out o))
                    object o = !cd.Successful ? argument : cd.Result;
                    if (cd.Ignore)
                        continue;
                    if (error != null)
                    {
                        return null;
                    }
                    if (o is T)
                    {
                        result.Add((T) o);
                        continue;
                    }
                    if (o is Missing)
                        continue;
                    throw new NotSupportedException();
                }
            }
            var array = new T[result.Count];
            result.CopyTo(array);
            return array;
        }

        private static double[] toDoubleArray(object[] args, IWorkbook workbook, IWorksheet worksheet, ref Error error,
                                              IParameterConverter converter, bool isParams)
        {
            if (converter == null)
                converter = CanonicalArgumentConverter.Instance;
            var result = new List<double>();
            bool already = false;
            foreach (object argument in args)
            {
                if (already && !isParams)
                    break;
                already = true;
                if (argument is IEnumerable<object>)
                {
                    var range = (IEnumerable<object>) argument;
                    foreach (object cell in range)
                    {
                        // bool ignore = false;
                        var cd = new ConversionData(workbook, worksheet, cell, typeof (double));
                        converter.Convert(cd);
                       // Debug.WriteLine(string.Format("converting {0}", cd));

                        //if (!converter.Convert(cell, typeof(double), worksheet, ref ignore, ref error, out o))
                        object o = !cd.Successful ? cell : cd.Result;

                        error = cd.Error;
                        if (cd.Error != null)
                            return null;
                        if (cd.Ignore)
                            continue;
                        result.Add((double) o);
                    }
                }
                else
                {
                    // bool ignore = false;
                    var cd = new ConversionData(workbook, worksheet, argument, typeof (double));
                    converter.Convert(cd);
                    //if (!converter.Convert(argument, typeof(double), worksheet, ref ignore, ref error, out o))
                    object o = !cd.Successful ? argument : cd.Result;
                    if (cd.Ignore)
                        continue;
                    error = cd.Error;
                    if (error != null)
                    {
                        return null;
                    }
                    if (o is int)
                        o = (double) (int) o;
                    if (o is double)
                    {
                        result.Add((double) o);
                        continue;
                    }
                    if (o is Missing)
                        continue;
                    throw new NotSupportedException();
                }
            }
            var array = new double[result.Count];
            result.CopyTo(array);
            return array;
        }

        static T[] ToTArray<T> (IEnumerable<object> args, IWorkbook workbook, IWorksheet worksheet, ref Error error, IParameterConverter converter)
        {
            if (converter == null)
                converter = CanonicalArgumentConverter.Instance;
            var result = new List<T>();
            foreach (object argument in args)
            {
                if (argument is IEnumerable<object> && typeof (T) != typeof (IRange))
                {
                    var range = (IEnumerable<object>)argument;
                    foreach (object cell in range)
                    {
                        //bool ignore = false;
                        var cd = new ConversionData(workbook, worksheet, cell, typeof(T));
                        converter.Convert(cd);
                        object o = !cd.Successful ? cell : cd.Result;
                        error = cd.Error;
                        if (error != null)
                            return null;
                        if (cd.Ignore)
                            continue;
#if DEBUG
                        cd = new ConversionData(workbook, worksheet, cell, typeof(T));
                        converter.Convert(cd);
#endif
                        result.Add((T) o);
                    }
                }
                else
                {
                    var cd = new ConversionData(workbook, worksheet, argument, typeof(T));
                    converter.Convert(cd);
                    object o = !cd.Successful ? argument : cd.Result;
                    if (cd.Ignore)
                        continue;
                    error = cd.Error;
                    if (error != null)
                    {
                        return null;
                    }
#if DEBUG
                    cd = new ConversionData(workbook, worksheet, argument, typeof(T));
                    converter.Convert(cd);
      
#endif
                    result.Add((T) o);
                }
            }
            var array = new T[result.Count];
            result.CopyTo(array);
            return array;
            
        }
    }
}